package com.sixbro.authorization.config;

import com.sixbro.authorization.service.OAuth2ClientDetailsService;
import com.sixbro.authorization.service.OAuth2UserDetailsService;
import com.sixbro.common.domain.Principal;
import com.sixbro.common.domain.Role;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.InMemoryApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 *
 * </p>
 *
 * @author: Mr.Lu
 * @since: 2021/12/6 14:25
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfiguration  extends AuthorizationServerConfigurerAdapter {
    private final PasswordEncoder passwordEncoder;
    private final OAuth2UserDetailsService userDetailsService;
    /**
     * 授权模式专用对象
     */
    private final AuthenticationManager authenticationManager;
    private final OAuth2ClientDetailsService clientDetailsService;

    public OAuth2AuthorizationServerConfiguration(
            PasswordEncoder passwordEncoder,
            OAuth2UserDetailsService userDetailsService,
            AuthenticationManager authenticationManager,
            OAuth2ClientDetailsService clientDetailsService
    ) {
        this.passwordEncoder = passwordEncoder;
        this.userDetailsService = userDetailsService;
        this.authenticationManager = authenticationManager;
        this.clientDetailsService = clientDetailsService;
    }

    /**
     * token保存策略 【token存储,这里使用jwt方式存储】
     * @return
     */
    @Bean
    public TokenStore tokenStore( ) {
        return new JwtTokenStore( jwtAccessTokenConverter() );
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter( ) {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory( new ClassPathResource( "jwt.jks" ), "Ym9zcy5kcmVhbQo=".toCharArray( ) );
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
            /***
             * 重写增强token方法,用于自定义一些token返回的信息
             */
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                Optional.ofNullable(authentication.getUserAuthentication()).ifPresent(userAuthentication -> {
                    Map< String, Object > additionalInformation = new LinkedHashMap<>();
                    /** 与登录时候放进去的UserDetail实现类一直查看link{SecurityConfiguration} */
                    Principal principal = (Principal) userAuthentication.getPrincipal();
                    /** 自定义一些token属性 ***/
                    additionalInformation.put( "principal_id", principal.getId( ) );
                    additionalInformation.put( "roles", principal.getRoles( ).stream().map( Role::getRole ).collect(Collectors.toList( )));
                    ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation( additionalInformation );
                });
                return super.enhance(accessToken, authentication);
            }
        };
        // 设置jwt加解密秘钥，不设置会随机一个
        accessTokenConverter.setKeyPair( keyStoreKeyFactory.getKeyPair( "jwt" ) );
        return accessTokenConverter;
    }

    /**
     * 授权信息保存策略
     * @return
     */
    @Bean
    public ApprovalStore approvalStore(){
        return new InMemoryApprovalStore();
    }

    /**
     * 授权码模式数据来源
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new InMemoryAuthorizationCodeServices();
    }

    /**
     * 指定客户端信息的数据库来源
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails( this.clientDetailsService );
    }

    /**
     * 检查token的策略 【oauth 的一些权限】
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .passwordEncoder( this.passwordEncoder )
                .allowFormAuthenticationForClients( )
                .tokenKeyAccess( "permitAll()" )
                .checkTokenAccess( "isAuthenticated()" );
    }

    /**
     * OAuth2的主配置信息 【token端点配置】
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .approvalStore(approvalStore())
                .authenticationManager( authenticationManager )
                .authorizationCodeServices(authorizationCodeServices())
                .accessTokenConverter( jwtAccessTokenConverter( ) )
                .tokenStore( tokenStore( ) )
                .userDetailsService( userDetailsService )
                .exceptionTranslator( new DefaultWebResponseExceptionTranslator( ) {
                    @Override
                    public ResponseEntity<OAuth2Exception> translate(Exception e ) throws Exception {
                        String name = e.getClass( ).getName( );
                        String path = String.format( "at %s", e.getStackTrace( )[ 0 ] );
                        System.err.println( name + ": " + e.getMessage( ) );
                        System.err.println( "\t" + path );
                        return super.translate( e );
                    }
                } );

        // 配置TokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setReuseRefreshToken(true);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.MINUTES.toSeconds(10));
        endpoints.tokenServices(tokenServices);
    }
}
